/*
 * =====================================================================================
 *
 *       Filename:  smt_httpd.c
 *
 *    Description:  The main source file of smt-httpd
 *
 *        Version:  1.0
 *        Created:  2011年03月22日 19时25分00秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Wang Xing (NJUID: 081251158) 
 *        Company:  NJU
 *
 * =====================================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>

#define LISTENQ 1024
#define SERVER_STRING "Server: smt-httpd/0.1.0\r\n"

#define ISspace(x) isspace((int)(x))

int listenfd;
int nthreads;	//the size of the thread pool
int idle_thread_num = 0;
socklen_t addrlen;

pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;

pthread_t *pthreads;	//the thread pool

int get_line(int sock, char *buf, int size);
void accept_client(int);
void thread_make(int);
void error_die(const char *message);
void serve_file(int client, const char *filename);
void headers(int client, const char *filename);
void not_found(int client);
void cat(int client, FILE *resource);

int main(int argc, char **argv)
{
	int i;
	int serv_port;
	struct sockaddr_in servaddr;

	if(argc == 3) {
		serv_port = atoi(argv[1]);

		listenfd = socket(AF_INET, SOCK_STREAM, 0);

		bzero(&servaddr, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
		servaddr.sin_port = htons(serv_port);

		bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

		listen(listenfd, LISTENQ);

		printf("The server is listening at port %d\n", serv_port);
	} else {
		fprintf(stderr, "usage: smt_httpd <port> <threads>\n");
		exit(1);
	}

	nthreads = atoi(argv[argc - 1]);
	if((pthreads = calloc(nthreads, sizeof(pthread_t))) == NULL ) {
		error_die("calloc error\n");
	}

	for(i = 0; i < nthreads; i++) {
		thread_make(i);
	}

	pthread_exit(NULL);
}

void error_die(const char *message)
{
	perror(message);
	exit(1);
}

/* 
 * The main function of a thread
 */
void* thread_main(void *arg)
{
    int i;
	int connfd;
	socklen_t clilen;
	struct sockaddr *cliaddr;

	cliaddr = malloc(addrlen);

	printf("thread %d starting\n", (int)arg);
	for(;;) {
		clilen = addrlen;
		
		/* 
		 * only one thread can accept
		 */
		pthread_mutex_lock(&mlock);
		connfd = accept(listenfd, cliaddr, &clilen);
		if(connfd == -1) {
			error_die("accept error\n");
		}
		pthread_mutex_unlock(&mlock);

        printf("a client is connecting\n");
		accept_client(connfd);

		printf("connection disconnected\n");
		close(connfd);

		/*
	     * note that every time we decrement idle_thread_num,
	     * we should check if the idle thread reaches zero,
	     * and if so, we need to re-fill the thread pool
	     */
	    idle_thread_num--;
	    if(idle_thread_num == 0) {
		    for(i = 0; i < nthreads; i++) {
			    thread_make(i);
		    }
	    }
	    
		pthread_exit(NULL);
	}
}

/* 
 * Make a thread
 */
void thread_make(int i)
{
	void *thread_main(void *);

	pthread_create(&pthreads[i], NULL,  &thread_main, (void *)i);
	idle_thread_num++;
	return;		//main thread returns
}

/*
 * This is the function to handle user request.
 * This is one of the main parts of smt-httpd
 */
void accept_client(int connfd)
{
	char buf[1024];
	int numchars;
	char method[255];
	char url[255];
	char path[512];
	size_t i, j;
	struct stat st;
	int cgi = 0;    // becomes true if server decides this is a CGI program
    char *query_string = NULL;

    numchars = get_line(connfd, buf, sizeof(buf));
    i = 0; j = 0;
    while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
    {
        method[i] = buf[j];
        i++; j++;
    }
    method[i] = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
        fprintf(stderr, "Method %s is not surpported\n", method);
        return;
    }

    if (strcasecmp(method, "POST") == 0)
        cgi = 1;

    i = 0;
    while (ISspace(buf[j]) && (j < sizeof(buf)))
        j++;
    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    {
        url[i] = buf[j];
        i++; j++;
    }
    url[i] = '\0';

    if (strcasecmp(method, "GET") == 0)
    {
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        if (*query_string == '?')
        {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

    sprintf(path, "htdocs%s", url);
    if (path[strlen(path) - 1] == '/')
        strcat(path, "index.html");
    if (stat(path, &st) == -1) {
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(connfd, buf, sizeof(buf));
        not_found(connfd);
    }
    else
    {
        if ((st.st_mode & S_IFMT) == S_IFDIR)
            strcat(path, "/index.html");
        if ((st.st_mode & S_IXUSR) ||
            (st.st_mode & S_IXGRP) ||
            (st.st_mode & S_IXOTH))
            cgi = 1;
        if (!cgi) {
            serve_file(connfd, path);
        } else {
            //TODO: execute_cgi(client, path, method, query_string);
            ;
        }
    }
	
}

int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n'))
    {
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0)
        {
            if (c == '\r')
            {
                n = recv(sock, &c, 1, MSG_PEEK);
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }
            buf[i] = c;
            i++;
        }
        else
            c = '\n';
    }
    buf[i] = '\0';

    return(i);
}

void serve_file(int client, const char *filename)
{
    FILE *resource = NULL;
    int numchars = 1;
    char buf[1024];

    buf[0] = 'A'; buf[1] = '\0';
    while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
        numchars = get_line(client, buf, sizeof(buf));

    resource = fopen(filename, "r");
    if (resource == NULL)
        not_found(client);
    else
    {
        headers(client, filename);
        cat(client, resource);
    }
    fclose(resource);
}

void headers(int client, const char *filename)
{
    char buf[1024];
    (void)filename;  /* could use filename to determine file type */

    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    strcpy(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    strcpy(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
}

void not_found(int client)
{
    char buf[1024];

    sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, SERVER_STRING);
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "Content-Type: text/html\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "your request because the resource specified\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "is unavailable or nonexistent.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

void cat(int client, FILE *resource)
{
    char buf[1024];

    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}
